Skip to content

Improvements to Chart 1 Rendering#25

Merged
beetlebugorg merged 16 commits into
mainfrom
tests/preslib-chart1
Jun 26, 2026
Merged

Improvements to Chart 1 Rendering#25
beetlebugorg merged 16 commits into
mainfrom
tests/preslib-chart1

Conversation

@beetlebugorg

Copy link
Copy Markdown
Owner

No description provided.

beetlebugorg and others added 16 commits June 26, 2026 06:06
…dth 1

S-57 best-available suppression keyed entirely on M_COVR(CATCOV=1) coverage,
so cells lacking M_COVR (e.g. the S-52 PresLib "ECDIS Chart 1" test cells)
got no suppression — a coarser cell's symbols double-drew over a finer cell
covering the same ground (the "smashed together" symbols).

extractCoverage now derives a coverage rectangle from a cell's data extent
when it has no M_COVR; the streaming coverage pass re-parses such a cell fully
so the fallback has its geometry. Derived rectangles are flagged covMeta.derived
and gate POINT suppression only (where a finer footprint supersedes a coarse
symbol), never area/line FILL suppression — a derived extent marks where a cell
IS, not where it has data, so a sparse cell wouldn't punch nodata holes.
Real NOAA cells always carry M_COVR ⇒ no behaviour change for them.

Also set the chart scale-boundary stroke to width 1 per S-52 §10.1.9.1
LS(SOLD,1,CHGRD) (was 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l size

NEWOBJ aliases to the point-only S-101 VirtualAISAidToNavigation rule, so its
line/area variants errored (drew nothing) and every NEWOBJ point stamped a V-AIS
mark — ignoring the producer's SYMINS instruction (e.g. SYMINS="SY(INFORM01)").
381 of 386 ECDIS-Chart-1 NEWOBJ carry SYMINS (164 TX labels, 91 LS, 80 SY, 27 AC,
17 LC, 2 AP) — that string IS the portrayal.

Resurrect SYMINS02 (was deleted with pkg/s52): parseSYMINS parses the
';'-separated SY/TX/TE/LS/LC/AC/AP ops into SymbolCall/DrawText/StrokeLine/
LinePattern/FillPolygon/PatternFill (reusing formatSubstitute for TE), hooked at
the top of buildFeature so a NEWOBJ-with-SYMINS overrides the V-AIS stream.
NEWOBJ without SYMINS falls back to a dashed magenta new-object boundary;
SWPARE (no SweptArea.lua in the catalogue) falls back to a dashed boundary +
"swept to <DRVAL1>" label.

Fix symbol physical size: DefaultPxPerSymbolUnit divided by 0.35278mm (the 1/72"
point) while the app measures the screen at 0.26458mm (1/96" CSS px), rendering
every symbol ~25% too small. Use 0.26458 so symbols hit their encoded size — the
S-52 size-check SY(CHKSYM01) box now measures ~5mm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`make preslib-chart1` (scripts/preslib-chart1.sh + .mjs) renders each S-52 PresLib
"ECDIS Chart 1" panel by reference-plot page number for visual diffing against the
spec (PresLib e4.0.0 Part I §16, doc pages 238-253): extracts the cells, serves a
throwaway server, imports via the normal server-side bake, screenshots each panel
framed to its cell, tears down. Output → testdata/preslib-chart1-out/ (gitignored).

TestS101Audit now also dumps a per-class error breakdown (which classes fail
portrayal and how often) — surfaced the NEWOBJ/SWPARE gaps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion scale

Add a "spec" mode (?spec / [spec]): a clean, full-bleed map with every floating
control, the status readout, attribution, load bar and the S-52 scalebar hidden —
for capturing reference-style plots. The scalebar lives in chart-canvas's shadow
root, so it's hidden at the source when an ancestor chart-plotter[-app] has [spec].

Rework the ECDIS-Chart-1 harness to render each page in spec mode at ITS
compilation scale: the viewport is sized to the cell's ground extent / CSCL /
pixel-pitch (1:14 000 harbor pages, 1:60 000 overview), so each panel is captured
full-screen at the scale the legend was drawn for — matching the reference figure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hooting out)

The AugmentedRay instruction carries the leg LENGTH's CRS: LocalCRS = display
millimetres (the 25 mm short sector leg), GeographicCRS = a fixed ground distance
in metres (a sectorLineLength or full-VALNMR leg). The parser ignored the CRS and
treated every length as mm, so a GeographicCRS leg of nmi2metres(0.1 NM)=185 m
rendered as 185 mm — ~10× too long, shooting the sector legs off the chart
(visible in the PresLib ECDIS-Chart-1 "Aids and services" panel).

Parse the length CRS into AugmentedGeom.LengthGroundM (metres) vs LengthMM, carry
it onto AugmentedFigure, and in tessellateFigure convert a ground-length leg to
pixels at the tile zoom (like the full-length leg) instead of mm. Sector legs now
render at their correct contained length.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
S-52 TX/TE labels are single-line, but the `text` layer set no text-max-width, so
MapLibre's 10-em default wrapped longer labels (e.g. "Information about chart
display (A,B)") onto a second line — which then collided and was dropped
(text-optional), so the label looked truncated. Set text-max-width: 40 so each
label stays on one line, matching the PresLib reference plots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0.6.1.1)

Objects carrying ancillary info (INFORM/NINFOM, or TXTDSC/NTXTDS/PICREP) now get
SY(INFORM01) at their position — a box-on-a-leader "info available" marker. This
is what produces the leader-line callouts on the PresLib "Approved new object
symbols" V-AIS (page 251), whose SYMINS draws only the ring+topmark; the callout
is the §10.6.1.1 indicator, not part of the symbol.

addInformSymbol appends it for any feature with a non-empty info attribute (all
build paths). The bake routes INFORM01 as display priority 8 / category Other
(overriding the host feature's category), so it clears Standard display and only
shows when the mariner enables Other — matching the spec. The Chart-1 harness
enables Other so the spec render shows the callouts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… harness

The IHO PresLib reference plots show ALL symbology, so set the harness mariner
explicitly rather than relying on defaults: display category Other on (INFORM01
callouts, "other marks", magnetic variation), data-quality overlay on (the CATZOC
"quality of data" panels), depths in metres (IHO, not NOAA feet), 25 mm short
sector legs + symbolized boundaries (the S-52 defaults the reference uses). Makes
the spec render deterministic and match the reference (e.g. page 243 depths now
read 5.5m/4m and the quality-of-data zones appear).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nk map)

With the basemap off, a remote installed cell was just a faint outline box you
had to spot by eye. Add a cell-name label at each coverage box's centroid, capped
at the band's render zoom (like the fill/line) so it vanishes once the chart
itself draws — visible when you're zoomed out and lost, gone when you're there.
Decluttered (drops on overlap). Tap-to-fly-in already worked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e + search

Finding an installed cell on a zoomed-out blank map didn't scale: coverage boxes
are per-pack (a single global box over a 7k-cell "import" set) and per-cell would
freeze the map. The scalable answer is search-by-name → fly-to.

Server: a small persistent name→bbox index over the cached cells
(<dataDir>/cells-index.json), backfilled once in the background by reading each
cell's header (baker.ParseCellBytes bounds), refreshed after import. GET
/api/cells now also returns a "bbox" map and accepts ?active=1 — only cells whose
footprint overlaps an ENABLED pack (charts actually on the map), and only those
indexed (an un-indexed cell has no footprint to fly to). No cell→pack table
needed; overlap with enabled-pack bounds defines "active".

Client: the search box's catalog is now the ACTIVE installed cells (name+bbox
from /api/cells?active=1), so typing a cell name finds it and flies to its
footprint — verified flying to AA5C1CDE on a blank map. (Search targets active
charts, not the NOAA discovery catalogue.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…know a name)

Search-by-name only helps if you know the name. Opening search with nothing
typed now lists the active installed charts — ordered NEAREST to the current view
first (most relevant to where you're looking), capped at 40 with a "N charts —
type to narrow" footer. Triggered on open, so you see your charts immediately and
can click one to fly there, or type to filter. Verified: empty search lists the
14 PresLib cells, nearest-first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The name→bbox index could drift: build() skipped already-indexed cells (so a
re-imported cell kept stale bounds) and never dropped entries for removed cells.
Now build() reconciles against ENC_ROOT — pruning entries whose cell is no longer
on disk — and an import forget()s the cells it re-caches so the rebuild re-parses
their bounds. (Search results were already remove-safe: serveCells lists live
ENC_ROOT dirs, looking up bbox in the index, so a removed cell never appears even
before the prune.) Add/update/remove all covered; verified by test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…slib-chart1)

`make s64-pages` (scripts/s64-pages.sh + .mjs) extracts the S-64 ENC TDS, serves
+ imports it, and renders each rendering test in spec mode at the cell's
compilation scale → testdata/s64-pages-out/ (gitignored), for diffing against the
S-64 reference plots. Unlike PresLib Chart 1 (one all-symbology plot), S-64 varies
the mariner per page — §3.1 renders the same area at Base / Standard / Other — and
uses the S-64 setup (safety contour/depth 10 m, symbolized boundaries, metres).
Covers §3.1, 3.2, 3.3, 3.4, 3.6, 3.7/3.7.7, 2.1.1, 5/6/7. §3.9 Polar (beyond
Web-Mercator) and §3.8.5 AML (non-ENC names) are omitted with a note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a Chart 1 page to the docs that embeds the S-52 PresLib "ECDIS Chart 1"
reference sheet LIVE — one read-only <chart-plotter> widget — and turns it into a
symbol-compliance checker: a list of every reference panel (PresLib §16, pp.
238–253) beside the chart; click one and the widget fitBounds() to that panel
(chrome-aware padding) so you can diff our render against the spec plot. The sheet
is one contiguous synthetic ENC, so it's a single ~1 MB tile bundle and navigation
is just the map camera.

- scripts/fetch-preslib-cells.sh + `make demo-chart1`: fetch the IHO PresLib draft
  (or a local testdata copy), bake the cells to a tile bundle that reuses the demo
  bundle's frontend assets (widget points its catalog= at the Chart 1 manifest).
- CI (docs.yml): cache the source cells + bake the bundle into docs/static/chart1
  alongside the existing Annapolis demo; gitignored, never committed.
- web: expose a public `map` getter on <chart-plotter> so embedders can frame a
  region with MapLibre's own fitBounds (used here, floored at the 1:139 000 SCAMIN
  so the whole-sheet fit never zooms features out of existence).
- README + docs intro: highlight ECDIS Chart 1 rendering as a feature.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…into tests/preslib-chart1

# Conflicts:
#	web/src/plugins/search-box.mjs
…ding

After merging main's physical-scale restage, onReady awaits the catalog load and
then calls addCatalogOverlay — but a setStyle (physical-scale / SCAMIN buckets) can
still be in flight at that point, so addSource threw "Style is not done loading".
The throw happened BEFORE the style.load self-heal listener was registered, so the
overlays never recovered. Bail when !isStyleLoaded() (the style.load handler
re-adds once ready) and guard the initial call so that listener always registers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@beetlebugorg beetlebugorg merged commit 2650309 into main Jun 26, 2026
4 checks passed
@beetlebugorg beetlebugorg deleted the tests/preslib-chart1 branch June 26, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant